package com.miyake.loveviewweb.server;

import java.util.Iterator;
import java.util.List;

import org.slim3.datastore.Datastore;

import com.google.appengine.api.datastore.Blob;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.Transaction;
import com.miyake.loveviewweb.server.service.MyUserKey;
import com.miyake.loveviewweb.shared.model.BinaryModel;
import com.miyake.loveviewweb.shared.model.DataColumnBody;
import com.miyake.loveviewweb.shared.model.DataColumnModel;
import com.miyake.loveviewweb.shared.model.DataModel;
import com.miyake.loveviewweb.shared.model.DataColumnModel.ColumnDataType;
import com.miyake.loveviewweb.shared.model.ImageDataBodyModel;
import com.miyake.loveviewweb.shared.model.ValueModel;
import com.miyake.loveviewweb.shared.model.XyDataBodyModel;

public class DataModelManager {
    protected void changeDataModel(DataModel dataModel) {
    }
    protected void changeXyDataBodyModel(XyDataBodyModel xyDataBodyModel) {
    }
    
    public DataModel storeXY(DataModel dataModel, List<DataColumnModel> dataColumnModels, 
            List<ValueModel> valueModels, byte[] originalData) {
        
        XyDataBodyModel xyDataBodyModel = buildDataModel(dataModel);
        
        if (dataColumnModels != null) {
            storeDataColumn(dataColumnModels, xyDataBodyModel);
        }
        
        if (valueModels != null) {
            storeValues(dataModel, valueModels);
        }
        
        if (originalData != null) {
            storeOriginalData(dataModel, originalData);
        }
        
        changeXyDataBodyModel(xyDataBodyModel);
        Datastore.put(xyDataBodyModel);
        changeDataModel(dataModel);
        Datastore.put(dataModel);
        
        return dataModel;
    }

    public DataModel storeXY(DataModel dataModel, List<DataColumnModel> dataColumnModels, 
            List<ValueModel> valueModels) {
        
        XyDataBodyModel xyDataBodyModel = buildDataModel(dataModel);
        
        if (dataColumnModels != null) {
            storeDataColumn(dataColumnModels, xyDataBodyModel);
        }
        
        if (valueModels != null) {
            storeValues(dataModel, valueModels);
        }
        
        changeXyDataBodyModel(xyDataBodyModel);
        Datastore.put(xyDataBodyModel);
        changeDataModel(dataModel);
        Datastore.put(dataModel);
        
        return dataModel;
    }
    
    public DataModel storeXY(DataModel dataModel, List<DataColumnModel> dataColumnModels) throws Exception {
        Transaction tx = Datastore.beginTransaction();
        
        try {
            XyDataBodyModel xyDataBodyModel = buildDataModel(dataModel);
            
            if (dataColumnModels != null) {
                storeDataColumn(dataColumnModels, xyDataBodyModel);
            }
            
            changeXyDataBodyModel(xyDataBodyModel);
            Datastore.put(xyDataBodyModel);
            changeDataModel(dataModel);
            Datastore.put(dataModel);
            
            tx.commit();
        }
        catch (Exception e) {
            e.printStackTrace();
            tx.rollback();
            throw e;
        }
        return dataModel;
    }
        
    private XyDataBodyModel buildDataModel(DataModel dataModel) {
        dataModel.setOwner(MyUserKey.get());
        XyDataBodyModel xyDataBodyModel = new XyDataBodyModel();
        if (dataModel.getKey() == null) {
            Datastore.put(dataModel);
        }
        xyDataBodyModel.setKey(Datastore.allocateId(dataModel.getKey(), XyDataBodyModel.class));
        dataModel.setDataBody(xyDataBodyModel.getKey());
        return xyDataBodyModel;
    }
    
    public void storeImage(DataModel dataModel, byte[] data, String contentType) {
        if (dataModel.getKey() == null) {
            Datastore.put(dataModel);
        }
        ImageDataBodyModel imageDataBodyModel = new ImageDataBodyModel();

        
        imageDataBodyModel.setKey(Datastore.allocateId(dataModel.getKey(), ImageDataBodyModel.class));
        Datastore.put(imageDataBodyModel);
        
        storeBinaryData(dataModel, data, contentType);
        
        // update DataModel
        dataModel.setDataBody(imageDataBodyModel.getKey());
        Datastore.put(dataModel);
    }
    
    private BinaryModel storeBinaryData(DataModel dataModel, byte[] data,
            String contentType) {
        if (dataModel.getKey() == null) {
            Datastore.put(dataModel);
        }
        BinaryModel bin = new BinaryModel();
        bin.setKey(Datastore.allocateId(dataModel.getKey(), BinaryModel.class));
        bin.setBlobData(new Blob(data));
        bin.setContentType(contentType);
        Datastore.put(bin);
        dataModel.setOriginalData(bin.getKey());
        Datastore.put(dataModel);
        
        return bin;
    }
    
    public void storeUnknow(DataModel dataModel, byte[] data, String contentType) {
        storeBinaryData(dataModel, data, contentType);
    }
    
    private void storeOriginalData(DataModel dataModel, byte[] originalData) {
        BinaryModel bin = new BinaryModel();
        Key binKey = Datastore.allocateId(dataModel.getKey(), BinaryModel.class);
        bin.setKey(binKey);
        bin.setBlobData(new Blob(originalData));
        Datastore.put(bin);
        dataModel.setOriginalData(binKey);
    }

    private void storeValues(DataModel dataModel, List<ValueModel> valueModels) {
        for (ValueModel valueModel : valueModels) {
            Key key = Datastore.allocateId(dataModel.getKey(), ValueModel.class);
            valueModel.setKey(key);
            Datastore.put(valueModel);
            dataModel.getValueKeys().add(key);
        }
    }

    private void storeDataColumn(List<DataColumnModel> dataColumnModels,
            XyDataBodyModel xyDataBodyModel) {
        if (dataColumnModels != null) {
            if (xyDataBodyModel.getDataColumnKeys().size() > 0) {
                for (Key key : xyDataBodyModel.getDataColumnKeys()) {
                    Datastore.deleteAll(key);
                }
                xyDataBodyModel.getDataColumnKeys().clear();
            }
            
            for (DataColumnModel dataColumnModel : dataColumnModels) {         
                dataColumnModel.setKey(Datastore.allocateId(xyDataBodyModel.getKey(), DataColumnModel.class));
                xyDataBodyModel.getDataColumnKeys().add(dataColumnModel.getKey());
                
                DataColumnBody columnBody = new DataColumnBody();
                columnBody.setValue(dataColumnModel.getValue());
                columnBody.setKey(Datastore.allocateId(dataColumnModel.getKey(), DataColumnBody.class));
                Datastore.put(columnBody);
                dataColumnModel.setBodyKey(columnBody.getKey());
                Datastore.put(dataColumnModel);
            }
        }
    }
    
    public void update(DataModel dataModel) {
        
        if (isUnknown(dataModel)) {
            
        }
        else if (isXyData(dataModel)) {
            // Requires transaction ////////////////////
            XyDataBodyModel xyDataBody = Datastore.get(XyDataBodyModel.class, dataModel.getDataBody());
            Iterator<DataColumnModel> it = dataModel.getXyDataBody().getDataColumn().iterator();
            for (Key key : xyDataBody.getDataColumnKeys()) {
                DataColumnModel originalCol = Datastore.get(DataColumnModel.class, key);
                DataColumnModel newCol = it.next();
                originalCol.setHeader(newCol.getHeader());
                DataColumnBody  body = Datastore.get(DataColumnBody.class, originalCol.getBodyKey());
                body.setValue(newCol.getValue());
                Datastore.put(originalCol);
                Datastore.put(body);
                if (!it.hasNext()) {
                    break;
                }
            } 
            
            while (it.hasNext()){
                DataColumnModel col = it.next();
                storeDataColumn(xyDataBody, col.getHeader(), col.getValue());
            }
        }
        else if (isImageData(dataModel)) {
            
        }

        Datastore.put(dataModel);
    }

    private boolean isImageData(DataModel dataModel) {
        return ImageDataBodyModel.class.getName().contains(dataModel.getDataBody().getKind());
    }

    private boolean isXyData(DataModel dataModel) {
        return XyDataBodyModel.class.getName().contains(dataModel.getDataBody().getKind());
    }
    
    private boolean isUnknown(DataModel dataModel) {
        return dataModel.getDataBody() == null;
    }
    
    private void storeDataColumn(XyDataBodyModel xyDataBody, String header, String[] arr) {
        // create column
        DataColumnModel dataColumn = new DataColumnModel();
        Key columnKey = Datastore.allocateId(xyDataBody.getKey(), DataColumnModel.class);
        dataColumn.setKey(columnKey);
        dataColumn.setHeader(header);
        dataColumn.setType(ColumnDataType.String);
        Datastore.put(dataColumn);
        //dataColumn.setDataKey(data.getKey());
        
        // Column Body
        DataColumnBody body = new DataColumnBody();
        body.setValue(arr);
        Key bodyKey = Datastore.allocateId(dataColumn.getKey(), DataColumnBody.class);
        body.setKey(bodyKey);
        Datastore.put(body);
        
        // set body key to column
        dataColumn.setBodyKey(body.getKey());
        Datastore.put(dataColumn);
        
        // set column key to data
        xyDataBody.getDataColumnKeys().add(columnKey);
        Datastore.put(xyDataBody);
    }

    public List<DataModel> fillDataModel(List<DataModel> dataModels, boolean content) {
        for (DataModel d : dataModels) {
            d = fillDataModel(d, content);
        }
        return dataModels;
    }
    
    public DataModel fillDataModel(DataModel dataModel, boolean content) {
        
        dataModel.getValues().addAll(getDislayValues(dataModel));
        
        if (isUnknown(dataModel)) {
            // do nothing
        }
        else if (isXyData(dataModel)) {
            XyDataBodyModel xyDataBody = Datastore.get(XyDataBodyModel.class, dataModel.getDataBody());
            
            List<DataColumnModel> dataColumns = null;

            dataColumns = Datastore.get(DataColumnModel.class, xyDataBody.getDataColumnKeys());
            xyDataBody.getDataColumn().addAll(dataColumns);
            
            if (content) {
                for (DataColumnModel dataColumnModel : dataColumns) {
                    DataColumnBody body = Datastore.get(DataColumnBody.class, dataColumnModel.getBodyKey());
                    dataColumnModel.setValue(body.getValue());
                }
            }
    
            if (dataModel.getOriginalData() != null) {
                dataModel.setOriginalDataUrl("/download" + "?key=" + Datastore.keyToString(dataModel.getKey()));
            }
            
            dataModel.setXyDataBody(xyDataBody);
        }
        else if (isImageData(dataModel)) {
            ImageDataBodyModel imageDataBody = Datastore.get(ImageDataBodyModel.class, dataModel.getDataBody());
            if (dataModel.getDataType().equals(DataModel.DataType.Image)) {
                imageDataBody.setUrl("/dataImage" + "?key=" + Datastore.keyToString(dataModel.getKey()));
            }            
            dataModel.setImageDataBody(imageDataBody);
        }
        return dataModel;
    }
    

    private List<ValueModel> getDislayValues(DataModel dataModel) {
        List<ValueModel> ret =  Datastore.get(ValueModel.class, dataModel.getValueKeys());
        for (ValueModel value : ret) {
            if (value.getSubUnit().equals("None")) {
                value.setSubUnit("");
            }
            else if (value.getSubUnit().equals("Pico")) {
                value.setSubUnit("p");
            }
            else if (value.getSubUnit().equals("Micro")) {
                value.setSubUnit("u");
            }
            else if (value.getSubUnit().equals("Nano")) {
                value.setSubUnit("n");
            }
            else if (value.getSubUnit().equals("Milli")) {
                value.setSubUnit("m");
            }
            else if (value.getSubUnit().equals("Kilo")) {
                value.setSubUnit("k");
            }
            else if (value.getSubUnit().equals("Mega")) {
                value.setSubUnit("M");
            }
            else if (value.getSubUnit().equals("Tera")) {
                value.setSubUnit("T");
            }
            
            if (value.getUnit().equals("None")) {
                value.setUnit("");
            }
        }
        return ret;
    }


}
